package ac.il.technion.cs236369.hw5.core.reports;

import static ac.il.technion.cs236369.hw5.core.utils.OperationResult.ERROR;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.io.IOUtils;
import org.apache.ecs.xml.XML;
import org.apache.log4j.Logger;

import ac.il.technion.cs236369.hw5.core.entities.Instrument;
import ac.il.technion.cs236369.hw5.core.entities.Order;
import ac.il.technion.cs236369.hw5.core.entities.OrderItem;
import ac.il.technion.cs236369.hw5.core.entities.Project;
import ac.il.technion.cs236369.hw5.core.entities.User;
import ac.il.technion.cs236369.hw5.core.entities.XsltStorage;
import ac.il.technion.cs236369.hw5.core.utils.OperationResult;

public enum ReportOperation {

	REPORT_PROJECT_SUMMARY( "project_summary"){
		private ThreadLocal<Project> project = new ThreadLocal<Project>( );
		private ThreadLocal<String> template = new ThreadLocal<String>( );
		public ReportOperation execute( HttpServletRequest request) {
			String projectId = null;
			if ( (projectId = request.getParameter( "projectId")) == null
					|| request.getParameter( "projectId").length( ) == 0) {
				status.set(ERROR);
				logger.warn( "Project id wasn't sent, canceling report operation");
				return this;
			}
			final int id = Integer.valueOf( projectId);
			final EntityManager manager = emf.createEntityManager( );
			try {
				Project p = manager.find( Project.class, id);
				manager.refresh( p);
				project.set( p);
				if ( project.get( ) == null) {
					status.set( OperationResult.ERROR);
					return this;
				}
				final String xsltId = request.getParameter( "xsltId");
				if ( xsltId == null) {
					status.set( OperationResult.ERROR);
					return this;
				}
				XsltStorage xslt = manager.find( XsltStorage.class, Integer.valueOf( xsltId));
				if ( xslt == null) {
					status.set( OperationResult.ERROR);
					return this;
				}
				template.set( xslt.getContent( ));
				status.set( OperationResult.SUCCESS);
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public ReportOperation result( HttpServletResponse response) {
			try {
				switch( status.get( )){
					case SUCCESS : {
						XML root = new XML( "report");
						/**
						 * Create project node.
						 */
						XML projectNode = createProjectNode( project.get( ));
						
						XML personalNode = new XML( "personal");
						/**
						 * Insert to report project personal list
						 */
						for( User user : project.get( ).getPersonal( )) {
							personalNode.addElement(  createUserNode( user));
						}
						projectNode.addElement( personalNode);
						
						XML ordersNode = new XML( "orders");
						for( Order order : project.get( ).getOrders( )) {
							ordersNode.addElement( createOrderNode( order));
						}
						projectNode.addElement( ordersNode);
						root.addElement( projectNode);
						
						InputStreamReader inputTemplate = new InputStreamReader(IOUtils.toInputStream(template.get( )));
						InputStreamReader inputXML = new InputStreamReader(IOUtils.toInputStream(root.toString( )));
						ByteArrayOutputStream result = new ByteArrayOutputStream( );
						TransformerFactory tFactory = TransformerFactory.newInstance();
						Transformer transformer;
						try {
							transformer = tFactory.newTransformer(new StreamSource(inputTemplate));
							transformer.transform(new StreamSource(inputXML), new StreamResult( result));
						} catch (TransformerException e) {
							e.printStackTrace();
						}
						
						response.getWriter( ).println( result.toString( ));
						break;
					}
					case ERROR :  {
						/**
						 * Report error in XML format
						 */
						XML errorNode = new XML( "error");
						errorNode.addElement( "Backend error. Check log file.");
						response.getWriter( ).println( errorNode.toString( ));
						break;
					}
				}
			} catch (Exception e) {
				logger.error( e.getMessage( ));
				status.set(ERROR);
			}
			return this;
		}
	},
	REPORT_USER_SUMMARY( "user_summary") {
		private ThreadLocal<User> user = new ThreadLocal<User>( );
		private ThreadLocal<String> template = new ThreadLocal<String>( );
		public ReportOperation execute( HttpServletRequest request) {
			final String userId = request.getParameter( "userId");
			if ( userId == null || userId.length( ) == 0) {
				status.set( OperationResult. ERROR);
				return this;
			}
			final EntityManager manager = emf.createEntityManager( );
			try {
				User u = manager.find( User.class, userId);
				manager.refresh( u);
				user.set( u);
				if ( user.get( ) == null) {
					status.set( OperationResult.ERROR);
					return this;
				}
				final String xsltId = request.getParameter( "xsltId");
				if ( xsltId == null) {
					status.set( OperationResult.SUCCESS);
					return this;
				}
				final XsltStorage xslt = manager.find( XsltStorage.class, Integer.valueOf( xsltId));
				if ( xslt == null) {
					status.set( OperationResult.ERROR);
					return this;
				}
				template.set( xslt.getContent( ));
				status.set( OperationResult.SUCCESS);
			} finally {
				manager.close( );
			}
			return this;
		}
		
		public ReportOperation result( HttpServletResponse response) {
			/**
			 * <report>
			 * 		<user>
			 * 			// Here place user relevant information
			 * 			// including every order and project.
			 * 		</user>
			 * </report>
			 */
			try {
				switch( status.get( )) {
					case SUCCESS : {
						XML root = new XML( "report");
						/**
						 * Create user node.
						 */
						XML userNode = createUserNode( user.get( ));
						
						Collection<Project> projects = user.get( ).getProjects( );
						
						XML projectsNode = new XML( "projects");
						
						for( Project project : projects){
							projectsNode.addElement( createProjectNode( project));
						}
						userNode.addElement( projectsNode);
						
						XML ordersNode = new XML( "orders");
						for( Order order : user.get( ).getOrders( )) {
							ordersNode.addElement( createOrderNode( order));
						}
						userNode.addElement( ordersNode);
						
						root.addElement( userNode);
						
						InputStreamReader inputTemplate = new InputStreamReader(IOUtils.toInputStream(template.get( )));
						InputStreamReader inputXML = new InputStreamReader(IOUtils.toInputStream(root.toString( )));
						ByteArrayOutputStream result = new ByteArrayOutputStream( );
						TransformerFactory tFactory = TransformerFactory.newInstance();
						Transformer transformer;
						try {
							transformer = tFactory.newTransformer(new StreamSource(inputTemplate));
							transformer.transform(new StreamSource(inputXML), new StreamResult( result));
						} catch (TransformerException e) {
							e.printStackTrace();
						}
						
						response.getWriter( ).println( result.toString( ));
						break;
					}
					case ERROR : {
						/**
						 * Report error in XML format
						 */
						XML errorNode = new XML( "error");
						errorNode.addElement( "Backend error. Check log file.");
						response.getWriter( ).println( errorNode.toString( ));
						break;
					}
				}
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	},
	REPORT_INSTRUMENT_SUMMARY( "instrument_summary") {
		private ThreadLocal<Instrument> instrument = new ThreadLocal<Instrument>( );
		private ThreadLocal<String> template = new ThreadLocal<String>( );
		public ReportOperation execute( HttpServletRequest request) {
			String instrumentId = request.getParameter( "instrumentId");
			status = new ThreadLocal<OperationResult>( ) {
				@Override
				protected OperationResult initialValue( ) {
					return OperationResult.SUCCESS;
				}
				
			};
			if ( instrumentId == null || instrumentId.length( ) == 0) {
				status.set( OperationResult.ERROR);
				return this;
			}
			final int id = Integer.valueOf( instrumentId);
			final EntityManager manager = emf.createEntityManager( );
			try {
				Instrument inst = manager.find( Instrument.class, id);
				manager.refresh( inst);
				instrument.set( inst);
				if ( instrument.get( ) == null) {
					status.set( OperationResult.ERROR);
					return this;
				}
				final String xsltId = request.getParameter( "xsltId");
				if ( xsltId == null) {
					status.set( OperationResult.SUCCESS);
					return this;
				}
				final XsltStorage xslt = manager.find( XsltStorage.class, Integer.valueOf( xsltId));
				if ( xslt == null) {
					status.set( OperationResult.ERROR);
					return this;
				}
				template.set( xslt.getContent( ));				
			} finally {
				manager.close( );
			}
			status.set( OperationResult.SUCCESS);
			return this;
		}
		
		public ReportOperation result( HttpServletResponse response) {
			try {
				switch( status.get( )) {
					case SUCCESS : {
						XML root = new XML( "report");
						
						XML instrumentNode = createInstrumentNode( instrument.get( ));

						Integer instrumentId = instrument.get( ).getId( );
						EntityManager manager = emf.createEntityManager( );
						List< Project> projects = Collections.<Project>emptyList( );
						try {
							projects = manager.createQuery( "SELECT p FROM Project p WHERE EXISTS  ( SELECT o FROM p.orders o WHERE EXISTS ( SELECT i FROM o.orderItems i WHERE i.instrument.Id = :id))",Project.class)
								.setParameter( "id", instrumentId)
								.getResultList( );
						}
						catch ( Exception e) {
							logger.error( e.getMessage( ));
						} finally {
							manager.close( );
						}
						XML projectsNode = new XML( "projects");
						for( Project project : projects) {
							XML projectNode = createProjectNode( project);
							Collection<Order> orders = project.getOrders( );
							XML ordersNode = new XML( "orders");
							for ( Order order : orders) {
								if ( ((OrderItem)order.getOrderItems( ).toArray( )[0]).getInstrument( ).getId( ).equals( instrument.get( ).getId( ))) {
									XML orderNode = createOrderNode( order);
									orderNode.addElement( createUserNode( order.getUser( )));
									ordersNode.addElement( orderNode);
								}
							}
							projectNode.addElement( ordersNode);
							projectsNode.addElement( projectNode);
						}
						instrumentNode.addElement( projectsNode);
						root.addElement( instrumentNode);
						InputStreamReader inputTemplate = new InputStreamReader(IOUtils.toInputStream(template.get( )));
						InputStreamReader inputXML = new InputStreamReader(IOUtils.toInputStream(root.toString( )));
						ByteArrayOutputStream result = new ByteArrayOutputStream( );
						TransformerFactory tFactory = TransformerFactory.newInstance();
						Transformer transformer;
						try {
							transformer = tFactory.newTransformer(new StreamSource(inputTemplate));
							transformer.transform(new StreamSource(inputXML), new StreamResult( result));
						} catch (TransformerException e) {
							e.printStackTrace();
						}
						
						response.getWriter( ).println( result.toString( ));
						break;
					}
					case ERROR : {
						/**
						 * Report error in XML format
						 */
						XML errorNode = new XML( "error");
						errorNode.addElement( "Backend error. Check log file.");
						response.getWriter( ).println( errorNode.toString( ));
						break;
					}
				}
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	},
	UNDEFINED( "undefined") {
		public ReportOperation execute( HttpServletRequest request) {
			return this;
		}
		
		public ReportOperation result( HttpServletResponse response) {
			XML errorNode = new XML( "error");
			errorNode.addElement( "Undefined report request.");
			try {
				response.getWriter( ).println( errorNode.toString( ));
			} catch (IOException e) {
				logger.error( e.getMessage( ));
			}
			return this;
		}
	};
	
	private String name;
	
	private static Map< String, ReportOperation> map = new HashMap< String, ReportOperation>( );
	
	protected EntityManagerFactory emf = Persistence.createEntityManagerFactory( "jpaProvider");
	
	protected Logger logger = Logger.getLogger( ReportOperation.class);
	
	static {
		for( ReportOperation $ : ReportOperation.values( )) {
			map.put( $.name, $);
		}
	};
	
	protected ThreadLocal<OperationResult> status = new ThreadLocal<OperationResult>( ) {
		@Override
		protected OperationResult initialValue( ) {
			return OperationResult.SUCCESS;
		}
	};
	
	private ReportOperation( String name) {
		this.name = name;
	}
	
	public static ReportOperation fromString( String name) {
		ReportOperation $ = map.get( name);
		if ( null != $)
			return $;
		return UNDEFINED;
	}
	
	@Override
	public String toString( ) {
		return name;
	}

	abstract public ReportOperation execute( HttpServletRequest request);
	
	abstract public ReportOperation result( HttpServletResponse response);

	protected XML createOrderNode( Order order) {
		XML orderNode = new XML( "order");
		orderNode.addAttribute( "id", order.getId( ));
		orderNode.addAttribute( "userId", order.getUser( ).getLoginName( ));
		orderNode.addAttribute( "projectId", order.getProject( ).getId( ));
		/**
		 * Insert order date node
		 */
		XML orderDateNode = new XML( "date");
		orderDateNode.addElement(  order.getOrderDate( ).toString( ));
		orderNode.addElement( orderDateNode);
		
		/**
		 * Insert start slot number 
		 */
		XML startSlotNode = new XML( "startSlot");
		startSlotNode.addElement( ((OrderItem)order.getOrderItems( ).toArray( )[0]).getStartSlot( ).toString( ) );
		orderNode.addElement( startSlotNode);
		
		/**
		 * Insert slots count 
		 */
		XML slotsCountNode = new XML( "slotsCount");
		slotsCountNode.addElement( ((OrderItem)order.getOrderItems( ).toArray( )[0]).getSlotsCount( ).toString( ));
		orderNode.addElement( slotsCountNode);
		
		/**
		 * Insert instrument information
		 */
		ArrayList<OrderItem> list = new ArrayList< OrderItem>( order.getOrderItems( ));
		Instrument instrument = list.get( 0).getInstrument( );
		orderNode.addElement( createInstrumentNode( instrument));
		return orderNode;
	}

	protected XML createInstrumentNode( Instrument instrument) {
		XML instrumentNode = new XML( "instrument");
		instrumentNode.addAttribute( "id", instrument.getId( ));
		
		/**
		 * Insert instrument description
		 */
		
		XML instrumentNameNode = new XML( "description");
		instrumentNameNode.addElement( instrument.getDescription( ));
		instrumentNode.addElement( instrumentNameNode);
		
		/**
		 * Insert instrument type
		 */
		
		XML instrumentTypeNode = new XML( "type");
		instrumentTypeNode.addElement( instrument.getType( ));
		instrumentNode.addElement( instrumentTypeNode);
		
		/**
		 * Insert instrument slot length
		 */
		
		XML instrumentSlotLengthNode = new XML( "slotLength");
		instrumentSlotLengthNode.addElement( instrument.getTimeSlot( ).toString( ));
		instrumentNode.addElement( instrumentSlotLengthNode);
		
		/**
		 * Insert instrument usage price per slot
		 */
		
		XML instrumentUsagePriceNode = new XML( "usagePrice");
		instrumentUsagePriceNode.addElement( Double.toString( instrument.getUsagePrice( )));
		instrumentNode.addElement( instrumentUsagePriceNode);
		return instrumentNode;
	}

	protected XML createUserNode( User user) {
		XML userNode = new XML( "user");
		userNode.addAttribute( "userId", user.getLoginName( ));
		/**
		 * Insert first name node
		 */
		XML firstNameNode = new XML( "firstName");
		firstNameNode.addElement( user.getFirstName( ));
		userNode.addElement( firstNameNode);
		/**
		 * Insert last name node
		 */
		XML lastNameNode = new XML( "lastName");
		lastNameNode.addElement( user.getLastName( ));
		userNode.addElement( lastNameNode);
		/**
		 * Insert phone number node
		 */
		XML phoneNumberNode = new XML( "phoneNumber");
		phoneNumberNode.addElement( user.getPhoneNumber( ));
		userNode.addElement( phoneNumberNode);
		/**
		 * Insert activity status information node
		 */
		XML activityNode = new XML( "activityStatus");
		activityNode.addElement( user.getActivityStatus( ).toString( ));
		userNode.addElement( activityNode);
		return userNode;
	}

	protected XML createProjectNode( Project project) {
		XML projectNode = new XML( "project");
		projectNode.addAttribute( "id", project.getId( ));
		projectNode.addAttribute( "name", project.getName( ));
		projectNode.addAttribute( "ownerId", project.getUser( ).getLoginName( ));
		projectNode.addAttribute( "budget", project.getBudget( ));
		XML projectDesc = new XML( "description");
		projectDesc.addElement( project.getDescription( ));
		projectNode.addElement( projectDesc);
		return projectNode;
	}
}